package scalab.core.component

import scalab.core.Provider
import scalab.utils.Reporter

import scala.collection.immutable.{Set, HashSet}
import scalab.core.gates.{Input, Output}
import scalab.core.dependencies.Dependency

trait Component extends Provider with Reporter{

	/**
	* The component identifier. Each component in 
	*	the build graph is represented by an identifier.
	*	This is used to load and store information from 
	*	and to the cache. The identifier does not only 
	*	represent the component but also its position 
	*	in the build graph as well as its connections 
	*	with other components.
	*/
	private[core] var identifier: String = _
	private[core] var index: Int = _
	private[core] def behaviorId = className + instanceId
	/**
	* Returns the component identifier.
	*/
	def id: String = identifier
	/**
	* Computes, updates and returns component's identifier.
	*/
	private[core] def updateId: String
	
	//private...
	val out: Output //= new Out(this)
	final def output: Output = out
	override lazy val component: Component = this
	def productDependencies = out.productDependencies //rename to products
	def productDirs: List[String]
	import scalab.core.utils.ProductDirectories
	def productDirectories: ProductDirectories = (this -> ProductDirectories()).asInstanceOf[ProductDirectories]
	def canonicalProductDirectories = productDirs map {d => Resource.canonicalPath(d)}

//  import scala.collection.mutable.HashSet

	var cnt = 0 // put this in component object...
	/**
	* Creates a new filter whose input is provisionned by the output of each supplied component, 
	*/
	def apply(n: String, sel: (Selector, Dependency) => Boolean, cs: Iterable[Component]): Selector = new Selector{

		cnt += 1
		override val name: String = n + "$" + cnt
		
		//override def instanceId: String = ""
		//val in = mandatoryIn
		cs map {c =>
			c -> in
		}

		val staticDependencies: Iterable[Dependency] = Nil

		def select(d: Dependency): Boolean = {
			//info("before: " + productDependencies)
			val res = sel(this,d)
			//info("after: " + productDependencies)
			res 
		}/*{
			var str: List[Any] = Nil
			cs foreach {c => str = c :: str ; fwd(c.out)}
			info(str)
			true
		}*/
		/*{
			var str: List[Any] = Nil
			this.ins foreach {i => i.sources foreach {s => str = s.component :: str ;fwd(s.component.out)}}
			info(str)
			true
		}*/
	}

	//def apply(name: String, ex: CustomComponent => Boolean, cs: Component*): Filter = apply(name, ex, cs)

	/**
	* Returns a new filter that yields the dependencies 
	*	yield by <code>this filter</code> plus all the 
	*	dependencies yield by each filter in <code>fs</code>.
	*
	* Duplicates are discarded. Howewer, hard and soft 
	*	dependencies are considered to be distinct.
	3 todo: make merge
	*/
	def union(cs: Iterable[Component]): Selector = apply("union", {(self: Selector, dep: Dependency) =>
			val allDeps = self.sourceSet
			/*self.providers foreach {p => self.forward(p.out)}
			true*/
			allDeps contains dep
		}, this :: cs.toList)

	def union(cs: Component*): Selector = union(cs)

	/**
	* Returns a new Filter that yields the 
	* <a href="http://en.wikipedia.org/wiki/Complement_%28set_theory%29">
	*	relative complement</a> of <code>f2</code> in <code>this</code>.
	*	More precisely, the new <code>Filter</code>, will yield the 
	*	dependencies yield by <code>this</code> filter minus all the dependencies
	* yield by each filter in <code>fs</code>. Note that a dependency is characterized only by its 
	*	resource (i.e a canonical path). If you want to make the distinction 
	*	between soft and hard dependencies, use <code>difference</code> instead.
	*/
	def drop(cs: Iterable[Component]): Selector = apply("drop", {(self: Selector, d: Dependency) =>
		!(cs exists {c => c.productDependencies exists {d2 => d2 sameResource d}})
	}, cs)

	def drop(cs: Component*): Selector = drop(cs)

	/**
	* Returns a filter that yields all the dependencies yield by <code>this</code>
	*	filter but the ones yield by <code>f2</code>. Note: makes the distinction between 
	*	soft and hard dependencies. If you want to select dependencies with respect 
	*	to their canonical path, use <code>drop</code> instead.
	*/
	def difference(cs: Iterable[Component]): Selector = apply("diff", {(self: Selector, d: Dependency) =>
		!(cs exists {c => c.productDependencies contains d})
	}, cs)

	def difference(cs: Component*): Selector = difference(cs)

	/**
	* Returns a filter that yields all the dependencies yield by <code>this</code>
	*	filter but those satisfying the predicate <code>p</code>.
	*/
	def discard(p: Dependency => Boolean, cs: Iterable[Component]): Selector = keep(p, cs).complement

	def discard(p: Dependency => Boolean, cs: Component*): Selector = discard(p, cs)

	def keep(p: Dependency => Boolean, cs: Iterable[Component]): Selector = apply("keep", {(self: Selector, d: Dependency) =>
		p(d)
	}, cs)

	def keep(p: Dependency => Boolean, cs: Component*): Selector = keep(p, cs)

	//def discard(p: Dependency => Boolean): Filter = 

	/*private[core] def purge(name: String, p: Dependency => Boolean, cs: Iterable[Component]): Filter = this(name, {self: CustomComponent =>
		self.providers foreach {provider => 
			val deps = provider.productDependencies
			//self.info("deps: " + deps)
			//bug in scala library? method filter of scala.collection.immutable.Set
			//does not actually remove the elements...
			val filteredDeps = deps.toList filter { d =>
				val res = !p(d)
				if(!res)
					self.info("discarded " + d.getPath)
				res
			}
			//self.info("filteredDeps: " + filteredDeps)
			self.submit(filteredDeps)
		}
		//self.submit(self.out.dependencies filter {d => !p(d)})
		true
	}, this :: cs.toList)

	private[core] def purge(name: String, p: Dependency => Boolean, cs: Component*): Filter = 
		purge(name, p, cs)*/

	private[core] def submit(dep: Dependency): Component = {
		//info("submitted: " + dep)
		out.setDeps(out.productDependencies + dep)
    this
	}

	private[core] def submit(deps: Iterable[Dependency]): Component = {
		//info("submitted: " + deps.mkString("\n","\n",""))
		out.setDeps(out.productDependencies ++ deps)
		this
	}

	def transmit(dep: Dependency): Component = submit(dep)
	def transmit(deps: Iterable[Dependency]): Component = {
		submit(deps)
	}

	def forward(c: Component): Component = {
		forward(c.out)
	}

	def forward(o: Output): Component = {
		submit(o.productDependencies)
	}

	def className: String = getClass.getName

	def instanceId: String

	Component.allComps = Component.allComps + this
	private val instanceNumber = Component.instanceNumber
	Component.instanceNumber += 1
}

object Component{
	private[core] var allComps: Set[Component] = HashSet.empty[Component]
	private[core] var instanceNumber = 0
	def getAll: Set[Component] = allComps

	def natureOrdered(c: Component): Ordered[Component] = new Ordered[Component]{
		def compare(that: Component): Int = {
			val lex: Int = c.className compareTo that.className
			if(lex != 0)lex
			else {
				val instance = c.instanceId compareTo that.instanceId
				if(instance != 0)instance
				else c.instanceNumber - that.instanceNumber
			}
		}
	}

	def indexOrdered(self: Component): Ordered[Component] = new Ordered[Component]{
		def compare(that: Component): Int = {
			self.index - that.index
		}
	}

	def orderComponents(cs: Iterable[Component]) = {
		import scala.collection.immutable.TreeSet
		(new TreeSet[Component]()(indexOrdered _)) ++ cs
	}
}
